﻿#include "stdafx.h"
#if _SB_WINDOWS_ && _QG_USE_DSHOW
#include "qg/qgenv.h"
#include "qg/qgimage.h"
#include "qg/qgmtrl.h"
#include "qg/qgdshow.h"

#include <amvideo.h>
#include <dvdmedia.h>

#include "streams.h"

#ifdef _MSC_VER
#	if _SB_64_
#		if _SB_DEBUG_
#			pragma comment(lib, "strmbase64_debug.lib")
#		else
#			pragma comment(lib, "strmbase64.lib")
#		endif
#	else
#		if _SB_DEBUG_
#			pragma comment(lib, "strmbase_debug.lib")
#		else
#			pragma comment(lib, "strmbase.lib")
#		endif
#	endif
#	pragma comment(lib, "winmm")
#endif

namespace _dshow
{
	// 텍스쳐 렌더러 UUID
	struct __declspec(uuid("{71771540-2017-11cf-ae26-0020afd79767}")) CLSID_TextureRenderer;
}

#define QGCF_EXT_24_RGB			(qgColorFormat)(0xF000 | QGCF16_RGBA)


//////////////////////////////////////////////////////////////////////////
// 다이렉트쇼 미디어용 텍스쳐
class qgDshowTexture : public CBaseVideoRenderer, public tpl::MemZero
{
	KCH_DERIVE(qgDshowTexture, CBaseVideoRenderer);

private:
	kSpinLock			_lock;
	kint				_flags;

	// 미디어 데이터
	kint				_size;
	kbyte*				_data;

	// 미디어 정보
	bool				_isv2;
	tpl::Point			_dim;		// media dimension
	qgColorFormat		_format;

	// 텍스쳐
	qgTexture*			_tex;
	qgPixelProp			_pixel;
	qgColorFormat		_texfmt;

public:
	qgDshowTexture(LPUNKNOWN punk, HRESULT* res, kint flags)
		: super_type(__uuidof(_dshow::CLSID_TextureRenderer), L"Texture Renderer", punk, res)
		, _lock(KCFS_INIT)
		, _flags(flags)
		, _format(QGCF_NONE)
		, _texfmt(QGCF32_RGBA)
	{
#if 0
		if (res)
			*res = S_OK;
#endif
	}

	~qgDshowTexture()
	{
		kobj_unload(_tex);
		k_delete(_data);
	}

public:
	virtual HRESULT DoRenderSample(IMediaSample *pMediaSample)
	{
		//
		kbyte* src;
		pMediaSample->GetPointer(&src);

		kint size = pMediaSample->GetSize();
		if (size > _size)
		{
			_size = size;
			_data = k_renew(_data, size, kbyte);
		}

		memcpy(_data, src, size);

		return S_OK;
	}

	virtual HRESULT SetMediaType(const CMediaType *pMediaType)
	{
		// 미디어 정보
		if (_isv2)
		{
			VIDEOINFOHEADER2* vi2 = (VIDEOINFOHEADER2*)pMediaType->Format();
			_dim.Set(vi2->bmiHeader.biWidth, vi2->bmiHeader.biHeight);
		}
		else
		{
			VIDEOINFOHEADER* vi = (VIDEOINFOHEADER*)pMediaType->Format();
			_dim.Set(vi->bmiHeader.biWidth, vi->bmiHeader.biHeight);
		}

		// 최대 해상도 정함
		if (_dim.x > 2048 || _dim.y > 2048)
			return VFW_E_TYPE_NOT_ACCEPTED;

		// 텍스쳐 다시 만들기
		kGuardSpinLock(_lock)
		{
			kobj_unload(_tex);

			_tex = qgTexture::Create(QGTEX_2D, _dim.x, _dim.y, 0, 0, QGTEXF_S2D | QGRESF_DYNAMIC | QGTEXF_IMAGE, _texfmt);
			if (!_tex)
				return VFW_E_TYPE_NOT_ACCEPTED;

			_texfmt = _tex->GetColorFormat();
			_pixel = qgColorSupp::ToPixelProp(_texfmt);
		}

		return S_OK;
	}

	virtual HRESULT CheckMediaType(const CMediaType *pMediaType)
	{
		//
		if (!pMediaType)
			return E_POINTER;

		//
		const GUID& fmt = *pMediaType->FormatType();
		if (fmt != FORMAT_VideoInfo && fmt != FORMAT_VideoInfo2)
			return E_INVALIDARG;

		//
		_isv2 = fmt == FORMAT_VideoInfo2 ? true : false;

#if _SB_DEBUG_ && 1
		kwchar wz[260];
		GUID t = *pMediaType->Type();
		GUID s = *pMediaType->Subtype();

		if (t != MEDIATYPE_Video)
		{
			StringFromGUID2(t, wz, 260 - 1);
			k_tracef(KLV_INFO, "dshowtex.CheckMediaType", "NO VIDEO : %ls", wz);
		}

		StringFromGUID2(s, wz, 260 - 1);
		k_tracef(KLV_INFO, "dshowtex.CheckMediaType", "SUBTYPE : %ls", wz);
#endif

		//
		HRESULT hr = E_FAIL;

		if (IsEqualGUID(*pMediaType->Type(), MEDIATYPE_Video))
		{
			const GUID& subtype = *pMediaType->Subtype();

			if (!K_OFLAG(_flags, QGDSHOWMEDF_FMT32))
			{
				if (IsEqualGUID(subtype, MEDIASUBTYPE_RGB24))
				{
					k_trace(KLV_INFO, "dshowtex.CheckMediaType", "VIDEO FORMAT: RGB24");
					_format = QGCF_EXT_24_RGB;
					hr = S_OK;
				}
			}
			else
			{
				if (IsEqualGUID(subtype, MEDIASUBTYPE_ARGB32))
				{
					k_trace(KLV_INFO, "dshowtex.CheckMediaType", "VIDEO FORMAT: ARGB32");
					_format = QGCF32_RGBA;
					hr = S_OK;
				}
				else if (IsEqualGUID(subtype, MEDIASUBTYPE_RGB32))
				{
					k_trace(KLV_INFO, "dshowtex.CheckMediaType", "VIDEO FORMAT: RGB32");
					_format = QGCF32_RGB;
					hr = S_OK;
				}
				else if (IsEqualGUID(subtype, MEDIASUBTYPE_RGB24))
				{
					k_trace(KLV_INFO, "dshowtex.CheckMediaType", "VIDEO FORMAT: RGB24");
					_format = QGCF_EXT_24_RGB;
					hr = S_OK;
				}
			}
		}

		return hr;
	}

	bool UpdateBuffer()
	{
		k_return_value_if_fail(_tex, false);
		k_return_value_if_fail(_data && _size > 0, false);

		//
		kGuardSpinLock(_lock)
		{
			qgTexLock lock;
			if (!_tex->Enter(&lock))
				return false;

			if (_texfmt == QGCF32_BGRA)
			{
				switch ((ksize_t)_format)
				{
					case QGCF32_RGB:
					case QGCF32_RGBA:
						qgCcmWork::COPY_BGR_32_32(_dim.x, _dim.y, (kint*)_data, 0, (kint*)lock.data, lock.pitch / 4, true);
						break;

					case QGCF_EXT_24_RGB:
						qgCcmWork::COPY_BGR_24_32(_dim.x, _dim.y, _data, 0, (kint*)lock.data, lock.pitch / 4, true);
						break;
				}
			}
			else
			{
				switch ((ksize_t)_format)
				{
					case QGCF32_RGB:
					case QGCF32_RGBA:
						qgCcmWork::COPY_32_32(_dim.x, _dim.y, (kint*)_data, 0, (kint*)lock.data, lock.pitch / 4, true);
						break;

					case QGCF_EXT_24_RGB:
						qgCcmWork::COPY_24_32(_dim.x, _dim.y, _data, 0, (kint*)lock.data, lock.pitch / 4, true);
						break;
				}
			}

			if (!_tex->Leave())
				return false;
		}

		return true;
	}

	// 텍스쳐를 비운다. 주로 정지 -> 재생시 팝업을 막기 위함
	bool ResetBuffer()
	{
		k_return_value_if_fail(_tex, false);

		if (_data && _size > 0)
			memset(_data, 0, _size);

		kGuardSpinLock(_lock)
		{
			qgTexLock lock;
			if (_tex->Enter(&lock))
			{
				memset(lock.data, 0, lock.pitch * lock.height);

				_tex->Leave();
			}
		}

		return true;
	}

	qgTexture* GetTexture()
	{
		return _tex;
	}

	bool IsFormatVersion2()
	{
		return _isv2;
	}

	const tpl::Point& GetDimension()
	{
		return _dim;
	}
};


//////////////////////////////////////////////////////////////////////////
// 다이렉트쇼 미디어
KIMPL_SUBCLASS(qgDshowMedia, "DshowMedia");



// 내부 데이터
class qgDshowMedia::ReqData : public tpl::MemZero
{
private:
	IGraphBuilder*		_gb;
	IMediaControl*		_mc;
	IMediaPosition*		_mp;
	IMediaEvent*		_me;
	IBaseFilter*		_rd;

public:
	ReqData()
		: _gb(NULL), _mc(NULL), _mp(NULL), _me(NULL), _rd(NULL)
	{
	}

	~ReqData()
	{
		Dispose();
	}

	bool Initialize(qgDevice* dev, kint flags)
	{
		HRESULT hr;

		//
		hr = CoCreateInstance(CLSID_FilterGraph, nullptr, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (LPVOID*)&_gb);
		if (FAILED(hr))
			return false;

		//
		qgDshowTexture* tex = new qgDshowTexture(NULL, &hr, flags);
		if (FAILED(hr))
		{
			delete tex;
			return false;
		}

		_rd = tex;
		_rd->AddRef();

		//
		hr = _gb->AddFilter(_rd, L"TEXTURERENDERER");
		if (FAILED(hr))
			return false;

		//
		_gb->QueryInterface(&_mc);
		_gb->QueryInterface(&_mp);
		_gb->QueryInterface(&_me);

		return true;
	}

	void Dispose()
	{
		kunk_release(_mc);
		kunk_release(_me);
		kunk_release(_mp);
		kunk_release(_gb);
		kunk_release(_rd);
	}

	bool UpdateTexture()
	{
		qgDshowTexture* tex = static_cast<qgDshowTexture*>(_rd);

		if (!tex->UpdateBuffer())
			return false;

		return true;
	}

	bool ResetTexture()
	{
		qgDshowTexture* tex = static_cast<qgDshowTexture*>(_rd);

		if (!tex->ResetBuffer())
			return false;

		return true;
	}

	qgTexture* GetTexture()
	{
		qgDshowTexture* tex = static_cast<qgDshowTexture*>(_rd);

		return tex->GetTexture();
	}

	bool SetFile(const kwchar* filename)
	{
		Close();

		//
		HRESULT hr;

		//
		IBaseFilter* sf;
		hr = _gb->AddSourceFilter(filename, L"SOURCE", &sf);
		if (FAILED(hr))
			return false;

#if 0
		PIN_INFO pi;
		IEnumPins* ep;
		if (SUCCEEDED(sf->EnumPins(&ep)))
		{
			IPin* p;
			ULONG u;
			while (SUCCEEDED(ep->Next(1, &p, &u)))
			{
				p->QueryPinInfo(&pi);
				if (u == 0)
					break;
			}
		}
#endif

		//
		IPin* po;
		hr = sf->FindPin(L"Output", &po);
		if (FAILED(hr))
		{
			hr = sf->FindPin(L"Video", &po);
			if (FAILED(hr))
			{
				hr = sf->FindPin(L"Raw Video 1", &po);
				if (FAILED(hr))
				{
					sf->Release();
					return false;
				}
			}
		}

		hr = _gb->Render(po);

		po->Release();
		sf->Release();

		//
		return SUCCEEDED(hr);
	}

	bool Close()
	{
		IBaseFilter* sf;
		if (FAILED(_gb->FindFilterByName(L"SOURCE", &sf)))
			return false;

		bool ret = SUCCEEDED(_gb->RemoveFilter(sf));
		sf->Release();

		return ret;
	}

	bool Play()
	{
		bool ret = !_mc ? false : SUCCEEDED(_mc->Run());

		UpdateTexture();

		return ret;
	}

	bool Stop()
	{
		bool ret = false;

		if (_mc)
		{
			if (SUCCEEDED(_mc->Stop()))
			{
				_mp->put_CurrentPosition(0);
				ret = true;
			}
		}

		ResetTexture();

		return ret;
	}

	bool Pause()
	{
		bool ret = !_mc ? false : SUCCEEDED(_mc->Pause());

		UpdateTexture();

		return ret;
	}

	klong GetLength()
	{
		if (!_mp)
			return -1;

		REFTIME len;
		if (FAILED(_mp->get_Duration(&len)))
			return -1;

		// 밀리초로 반환
		return (klong)(len * 1000.0f);
	}

	klong GetPosition()
	{
		if (!_mp)
			return -1;

		REFTIME pos;
		if (FAILED(_mp->get_CurrentPosition(&pos)))
			return -1;

		// 밀리초로 반환
		return (klong)(pos * 1000.0);
	}

	void SetPosition(klong setpos)
	{
		if (!_mp)
			return;

		// 초로 변경
		REFTIME pos = (REFTIME)setpos / 1000.0;
		if (pos < 0.0)
			pos = 0.0;

		_mp->put_CurrentPosition(pos);
	}

	bool HasVideo()
	{
		qgDshowTexture* tex = static_cast<qgDshowTexture*>(_rd);
		return tex != NULL;
	}

	long CheckEvent()
	{
		if (!_me)
			return 0;

		long code = 0;
		LONG_PTR l1, l2;
		HRESULT hr = _me->GetEvent(&code, &l1, &l2, 0);

		if (SUCCEEDED(hr))
		{
			if (code == EC_COMPLETE)
			{
				// 루프 기능을 추가할 때 주석을 뺄것
				//hr=_mp->put_CurrentPosition(0.0);
			}

			hr = _me->FreeEventParams(code, l1, l2);
		}

		return code;
	}

	const tpl::Point& GetVideoDimension()
	{
		qgDshowTexture* tex = static_cast<qgDshowTexture*>(_rd);
		return tex->GetDimension();
	}
};

//
void qgDshowMedia::TypeInitialize(kType type) throw()
{
	// COM 초기화
	CoInitialize(NULL);
}

//
qgDshowMedia::qgDshowMedia()
: super_type(QGROAM_OBJ_1)
, _req(new ReqData())
, _state(QGDSHOW_STATE_STOP)
{
}

//
qgDshowMedia::~qgDshowMedia()
{
	delete _req;
	kobj_unload(_texhold);
}

//
bool qgDshowMedia::Open(const char* filename)
{
	// 리얼 파일 밖에 안된다!!!
	tpl::ScopedMemPtrAlloc<char*> path(_dev->FilePath(filename));

	if (!path || !k_file_exist(path))
		return false;

	kwchar wz[260];
	k_conv_utf8_to_utf16(wz, 260 - 1, path);

	_state = QGDSHOW_STATE_STOP;

	if (!_req->SetFile(wz))
	{
		_hasvideo = false;
		_hasaudio = false;
		return false;
	}
	else
	{
		_hasvideo = _req->HasVideo();
		return true;
	}
}

//
bool qgDshowMedia::OpenL(const kwchar* filename)
{
	k_return_value_if_fail(filename, NULL);

	char sz[260];
	k_conv_utf16_to_utf8(sz, 260 - 1, filename);

	return Open(sz);
}

//
bool qgDshowMedia::Close()
{
	kobj_tracet(KLV_ERROR, KERR_NOTIMPL, "close function not support yet.");

	return false;
}

//
bool qgDshowMedia::Play(bool isplay)
{
	if (isplay)
	{
		if (_state == QGDSHOW_STATE_STOP || _state == QGDSHOW_STATE_PAUSE)
		{
			_state = QGDSHOW_STATE_PLAY;
			return _req->Play();
		}
	}
	else
	{
		if (_req->Stop())
		{
			_state = QGDSHOW_STATE_STOP;
			return true;
		}
	}

	return false;
}

//
bool qgDshowMedia::Stop(bool isstop)
{
	if (isstop)
	{
		if (_req->Stop())
		{
			_state = QGDSHOW_STATE_STOP;
			return true;
		}
	}
	else
	{
		if (_state == QGDSHOW_STATE_STOP || _state == QGDSHOW_STATE_PAUSE)
		{
			_state = QGDSHOW_STATE_PLAY;
			return _req->Play();
		}
	}

	return 0;
}

//
bool qgDshowMedia::Pause()
{
	if (_state == QGDSHOW_STATE_PLAY && _req->Pause())
	{
		_state = QGDSHOW_STATE_PAUSE;
		return true;
	}

	return false;
}

//
kint qgDshowMedia::GetVideoWidth()
{
	return _req->GetVideoDimension().x;
}

//
kint qgDshowMedia::GetVideoHeight()
{
	return _req->GetVideoDimension().y;
}

//
klong qgDshowMedia::GetLength()
{
	return _req->GetLength();
}

//
klong qgDshowMedia::GetPosition()
{
	return _req->GetPosition();
}

//
void qgDshowMedia::SetPosition(klong pos)
{
	_req->SetPosition(pos);
}

//
void qgDshowMedia::TurnLoop(bool isloop)
{
	_loop = isloop;
}

//
bool qgDshowMedia::Update()
{
	if (_state == QGDSHOW_STATE_PLAY)
	{
		long code = _req->CheckEvent();

		if (code == EC_COMPLETE)
		{
			if (!_loop)
				_state = QGDSHOW_STATE_PAUSE;
			else
			{
				_req->SetPosition(0);
				_req->Play();
			}
		}
	}

	// 위에서 PLAY로 바뀌었을 수도 있으므로 함더
	if (_state == QGDSHOW_STATE_PLAY)
		return _req->UpdateTexture();

	// 위에서 처리가 안되면 홀드
	if (_texhold)
		return true;

	return false;
}

//
qgTexture* qgDshowMedia::GetTexture(bool use_hold_as_possible)
{
	if (!_texhold)
		return _req->GetTexture();
	else
	{
		if (_state == QGDSHOW_STATE_STOP)
			return _texhold;
		else if (use_hold_as_possible && _state == QGDSHOW_STATE_PAUSE)
			return _texhold;
		else
			return _req->GetTexture();
	}
}

//
void qgDshowMedia::SetHoldTexture(qgTexture* tex)
{
	kobj_unload(_texhold);
	_texhold = tex;
	kobj_load(tex);
}

//
qgTexture* qgDshowMedia::GetHoldTexture()
{
	return _texhold;
}

//
qgDshowMedia* qgDshowMedia::New(kint flags, const char* filename)
{
	qgDshowMedia* self = k_new_obj(qgDshowMedia);

	if (!self->_req->Initialize(qgDevice::DeviceInstance, flags))
	{
		self->Unload();
		self = NULL;
	}
	else if (filename)
	{
		if (!self->Open(filename))
		{
			self->Unload();
			self = NULL;
		}
	}

	return self;
}

#endif	// _SB_WINDOWS_ && _QG_USE_DSHOW

